﻿/*
 * Cratos Entity
 * Cratos Entity class
 * 
 * Copyright (c) 2011-2012 Draco
 * 
 * This file is part of Cratos.
 * 
 * Cratos is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * Cratos is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Cratos.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *       .==.        .==.
 *      //'^\\      //^'\\
 *     // ^ ^\(\__/)/^ ^^\\
 *    //^ ^^ ^/6  6\ ^^ ^ \\
 *   //^ ^^ ^/( .. )\^ ^ ^ \\
 *  // ^^ ^/\| V""V|/\^ ^ ^ \\
 * //^^/\/ /  '~~'  \ \/\^ ^ \\
 *  ------------------------------------
 *  HERE BE DRAGONS
 *  
 *  Johan Dercksen - johan@draco.co.za
 */
using System;
using System.Text;
using System.Linq;
using System.Collections.Generic;

using Cratos.Tools.General;
using Cratos.Tools.DataObject;

namespace Cratos.Server.Entity
{
	/// <summary>
	/// Cratos Entity class
	/// </summary>
	public class CratosEntity : CratosEntityBase
	{
		#region Class Constant declarations
		#endregion

		#region Class Event declarations
		#endregion

		#region Class Variable declarations
		private IDOCratos dataObject;
		#endregion

		#region Class Constructors & Destructors
		/// <summary>
		/// Class Constructor
		/// </summary>
		public CratosEntity() : base() { }
		#endregion

		#region Class Properties
		#endregion

		#region Class Methods
		/// <summary>
		/// Perform Action
		/// </summary>
		/// <param name="cratosDO">Cratos Data Object</param>
		/// <returns>A boolean indicating the success of the method</returns>
		public override bool PerformAction(ref IDOCratos cratosDO)
		{
			// Declare the required variables
			bool returnValue = false;

			try
			{
				// Reset all the relevant values
				this.Errors.Clear();

				// Make sure we are performing an action
				if (cratosDO.DOAction == DOCratosAction.None) { return (true); }

				// Save the relevant values
				this.dataObject           = cratosDO;
				this.dataObject.IsLoading = true;

				// Validate the Data Object
				if (this.dataObject.Validate())
				{
					// Setup the DB Connection
					if (this.DBConnect(this.IgnoreUnity, this.dataObject.DBConnectionName, this.dataObject.DBActionType))
					{
						// Process all the Child Dependant embedded objects
						if (this.dataObject.DOAction != DOCratosAction.Retrieve) { this.ProcessEmbedded(false); }

						// Setup the Database Command
						if (!this.DBSetupSingleCommand(this.dataObject)) { return(returnValue); }

						// Setup the Database Parameters
						this.DBSetupSingleParameters(this.dataObject);

						// Make sure we have no errors before executing
						if (this.Errors.Count <= 0)
						{
							#region Execute the stored procedure and process results
							// Set the default Action Result
							this.dataObject.DOActionResult = DOCratosActionResult.Error;

							// Handle the relevant output if expected
							if (this.DBActionAttribute.IsExpectOutput)
							{
								#region Action Expects output
								// Execute the Action
								if (this.DBExecuteReader() && this.DBReaderHasRows)
								{
									// Populate the Data Object
									this.DBLoadDataFromReader((IDOCratos)this.dataObject);

									// Update the relevant object(s)
									this.dataObject.DOActionResult = DOCratosActionResult.Success;
									this.dataObject.IsLoaded = true;

									// Process all the embedded objects (None and Parent Dependant)
									this.ProcessEmbedded(true);
								}
								#endregion
							}
							else
							{
								#region No Output Expected
								// Execute the Action
								this.DBExecuteNonQuery();

								// Update the Action Result
								this.dataObject.DOActionResult = DOCratosActionResult.Success;

								// Process all the embedded objects (None and Parent Dependant)
								this.ProcessEmbedded(true);
								#endregion
							}
							#endregion
						}

						// Close the DB Connection
						this.DBDisconnect();
					}
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("CratosEntity.PerformAction", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}
			finally
			{
				// Update the return value
				returnValue = (this.Errors.Count == 0);

				// Reset the relevant values
				if (this.dataObject != null)
				{
				  this.dataObject.IsLoading = false;
				  this.dataObject.DOAction  = DOCratosAction.None;
				}
			}

			// Return to calling method
			return (returnValue);
		}
		#endregion

		#region Class Support Methods
		private bool ProcessEmbedded(bool isPostProcessing)
		{
			// Declare the required variables
			bool														returnValue = false;
			object													sourceData;
			object													embeddedCollection;
			IDOCratos												embeddedObject;
			DOCratosAction									parentAction;
			DOCratosEmbeddedKeyAttribute[]	embeddedKeyAttributes;

			try
			{
				// Update the Parent Data Object Action
				parentAction = this.dataObject.DOAction;

				// Process all the embedded objects
				foreach (DOCratosEmbeddedAttribute currentEmbedded in this.dataObject.AllDOEmbedded)
				{
					// Make sure we should process this Embedded object
					if ((isPostProcessing && (parentAction == DOCratosAction.Retrieve)) ||
							(isPostProcessing && (parentAction != DOCratosAction.Retrieve) && (currentEmbedded.Dependancy != DOCratosDependancy.Child)) ||
							(!isPostProcessing && (parentAction != DOCratosAction.Retrieve) &&
								((currentEmbedded.Dependancy != DOCratosDependancy.None) && (currentEmbedded.Dependancy != DOCratosDependancy.Parent)))
						 )
					{
						// Retrieve the relevant attributes
						embeddedKeyAttributes = (DOCratosEmbeddedKeyAttribute[])this.dataObject.GetType().GetProperty(currentEmbedded.Property).GetCustomAttributes(typeof(DOCratosEmbeddedKeyAttribute), false);

						// Handle the Single / collection Embedded object
						if (currentEmbedded.IsCollection)
						{
							#region Collection Handling
							// Retrieve the object and associated Methods and Properties
							embeddedCollection = DOCratosHelper.GetPropertyValue(this.dataObject, currentEmbedded.Property);

							// Update the relevant Properties
							DOCratosHelper.SetPropertyValue(embeddedCollection, "IgnoreUnity", this.IgnoreUnity);
							DOCratosHelper.SetPropertyValue(DOCratosHelper.GetPropertyValue(embeddedCollection, "DataObject"),
																							"DBConnectionName", this.dataObject.DBConnectionName);

							// Perform the relevant action if required
							if (((DOCratosCollectionAction)DOCratosHelper.GetPropertyValue(embeddedCollection, "CollectionAction")) != DOCratosCollectionAction.None)
							{
								#region Parameter / Filter Processing
								// Set the relevant Embedded Key data
								foreach (DOCratosEmbeddedKeyAttribute currentKey in embeddedKeyAttributes)
								{
									// Retrieve the relevant Source Data
									sourceData = this.dataObject.GetType().GetProperty(currentKey.ParentProperty).GetValue(this.dataObject, null);

									// Update the Key Property
									DOCratosHelper.SetPropertyValue(DOCratosHelper.GetPropertyValue(embeddedCollection, "DataObject"),
																									currentKey.EmbeddedKeyProperty, sourceData);
								}
								#endregion

								// Send the request and handle any errors
								if (!((bool)DOCratosHelper.InvokeMethod(embeddedCollection, "PerformAction", null)))
								{
									foreach (CratosError currentError in ((IEnumerable<object>)DOCratosHelper.GetPropertyValue(embeddedCollection, "Errors")))
									{
										this.Errors.Add(currentError);
									}
								}
							}
							#endregion
						}
						else
						{
							#region Single Object Handling
							// Retrieve the object
							embeddedObject                  = (IDOCratos)this.dataObject.GetType().GetProperty(currentEmbedded.Property).GetValue(this.dataObject, null);
							embeddedObject.IgnoreUnity      = this.IgnoreUnity;
							embeddedObject.DBConnectionName = this.dataObject.DBConnectionName;

							// Perform the relevant action if required
							if (embeddedObject.DOAction != DOCratosAction.None)
							{
								#region Parameter / Filter Processing
								// Set the relevant Embedded Key data
								foreach (DOCratosEmbeddedKeyAttribute currentKey in embeddedKeyAttributes)
								{
									// Retrieve the relevant Source Data
									sourceData = this.dataObject.GetType().GetProperty(currentKey.ParentProperty).GetValue(this.dataObject, null);

									// Update the Key Property
									embeddedObject.GetType().GetProperty(currentKey.EmbeddedKeyProperty).SetValue(embeddedObject, sourceData, null);
								}
								#endregion

								// Send the request and handle any errors
								if (!embeddedObject.PerformAction()) { foreach (CratosError currentError in embeddedObject.Errors) { this.Errors.Add(currentError); } }
							}
							#endregion
						}
					}
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("CratosEntity.ProcessEmbedded", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}
			finally
			{
				// Update the return value
				returnValue = (this.Errors.Count == 0);
			}

			// Return to calling method
			return (returnValue);
		}
		#endregion
	}
}
